use std::ops::Deref;

struct Foo {}

impl Foo {
    fn do_something(&self) {
        println!("Foo's do_something called");
    }
}

struct Mutex<T> {
    // We keep a reference to our data: T here.
    data: T,
}

struct MutexGuard<'a, T: 'a> {
    data: &'a T,
    // ...
}

// Locking the mutex is explicit.
impl<T> Mutex<T> {
    fn lock(&self) -> MutexGuard<T> {
        // Lock the underlying OS mutex.
        // ...

        // MutexGuard keeps a reference to self.
        MutexGuard {
            data: &self.data,
            // ....
        }
    }
}

// Destructor for unlocking the mutex.
impl<'a, T> Drop for MutexGuard<'a, T> {
    fn drop(&mut self) {
        // Unlock the underlying OS mutex.
        // ...
        println!("MutexGuard's drop called");
    }
}

// Implementing Deref means we can treat MutexGuard like a pointer to T.
impl<'a, T> Deref for MutexGuard<'a, T> {
    type Target = T;

    fn deref(&self) -> &T {
        self.data
    }
}

fn baz(foo_mutex: Mutex<Foo>) {
    let foo_lock = foo_mutex.lock();
    foo_lock.do_something();
    // do_something is a method on Foo.
    // The borrow checker ensures we can't store a reference to the underlying
    // Foo which will outlive the guard foo_lock.

    // foo_mutex is unlocked when we exit this function and foo_lock's
    // destructor is executed.
}

fn main() {
    let foo_data = Foo {};
    let foo_mutex = Mutex::<Foo> { data: foo_data };
    baz(foo_mutex);
}
